﻿/*
   Copyright 2011 Oren Nachman

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Net;
using System.Diagnostics;
using System.Threading;
using System.IO;

namespace AppLookup
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public class AppLookupData
        {
            public Guid Guid { get; set; }
        }

        private const string DOMAIN          = "catalog.zune.net";
        private const string XML_TITLE_OPEN  = "<a:title type=\"text\">";
        private const string XML_TITLE_CLOSE = "</a:title>";

        private HttpWebRequest _request;

        private bool _isWorking = false;
        public bool IsWorking
        {
            get { return _isWorking; }
            set
            {
                if (_isWorking != value)
                {
                    _isWorking = value;

                    NotifyPropertyChanged("IsWorking");

                    EnableLookupButton();
                }
            }
        }

        private string _error = "";
        public string Error
        {
            get { return _error; }
            set
            {
                if (_error != value)
                {
                    _error = value;

                    NotifyPropertyChanged("Error");
                }

                // update IsKnown even if the error didn't change
                IsKnown = (_error == "");
            }
        }

        private bool _isKnown = true;
        public bool IsKnown
        {
            get { return _isKnown; }
            set
            {
                if (_isKnown != value)
                {
                    _isKnown = value;

                    NotifyPropertyChanged("IsKnown");
                }
            }
        }

        private string _appName;
        public string AppName
        {
            get { return _appName; }
            set
            {
                if (_appName != value)
                {
                    _appName = value;

                    NotifyPropertyChanged("AppName");

                    IsKnown = true;
                }
            }
        }

        private string _appUri;
        public string AppUri
        {
            get { return _appUri; }
            set
            {
                if (_appUri != value)
                {
                    _appUri = value;

                    NotifyPropertyChanged("AppUri");
                }
            }
        }

        private string AppHttpUri { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            this.DataContext = this;

            txtGuid.Focus();

            // Kick off a fake background request to kick off proxy lookup.
            // This is generally a non-issue but for some machines (especially
            // when connected to a VPN) the first request can take a qhile so if
            // we can get it started earlier we can possibly save the user a little time)
            BackgroundResolver.Do(DOMAIN);
        }

        private void Window_Closing(object sender, CancelEventArgs e)
        {
            BackgroundResolver.Abort();
        }

        private void btnLookup_Click(object sender, RoutedEventArgs e)
        {
            IsWorking = true;
            btnLookup.IsEnabled = false;

            // clear everything
            ClearData();

            // assume the best!
            IsKnown = true;

            Thread thread = new Thread(new ParameterizedThreadStart(Lookup));
            thread.Start(txtGuid.Text);
        }

        private void ClearData()
        {
            AppUri     = "";
            AppHttpUri = "";
            AppName    = "";
            Error      = "";
        }

        private void Lookup(object guid)
        {
            Guid? verifiedGuid = null;

            try
            {
                verifiedGuid = CleanGuid((string)guid);
            }
            catch (FormatException)
            {
                Dispatcher.BeginInvoke(
                    new Action(delegate()
                    {
                        Error = "Invalid GUID";
                        IsWorking = false;
                    }),
                null);

                return;
            }

            // Url: http://catalog.zune.net/v3.2/en-US/apps/e4571a02-0b87-e011-986b-78e7d1fa76f8/?version=latest&clientType=ALL&store=Zest

            _request =
                (HttpWebRequest)WebRequest.Create(
                    "http://" + DOMAIN + "/v3.2/en-US/apps/" +
                    verifiedGuid.ToString() +
                    "/?version=latest&clientType=WinMobile%207.0&store=Zest"
                );

            _request.BeginGetResponse(new AsyncCallback(ZuneResponse), new AppLookupData() { Guid = (Guid)verifiedGuid });

        }

        private void ZuneResponse(IAsyncResult asynchronousResult)
        {
            byte[] buffer = new byte[2056];
            string xml = "";
            int indexTitleOpen, indexTitleClose;
            string appName = "";
            bool webRequestSucceeded = true;
            HttpWebResponse response = null;

            AppLookupData data = asynchronousResult.AsyncState as AppLookupData;

            if (data == null)
                throw new ArgumentException("Invalid Async state, must be of type AppLookupData");

            try
            {
                response = (HttpWebResponse)_request.EndGetResponse(asynchronousResult);
            }
            catch (WebException)
            {
                webRequestSucceeded = false;
            }

            if (webRequestSucceeded && response != null)
            {
                Stream stream = response.GetResponseStream();

                // read out the whole XML string
                while (stream.Read(buffer, 0, buffer.Length) > 0)
                {
                    xml += UTF8Encoding.UTF8.GetString(buffer);
                }

                // find the index of the opening and closing tags
                // TODO: XML?
                indexTitleOpen = xml.IndexOf(XML_TITLE_OPEN) + XML_TITLE_OPEN.Length;
                indexTitleClose = xml.IndexOf(XML_TITLE_CLOSE);

                if (indexTitleOpen > -1 && indexTitleClose > -1)
                {
                    appName = xml.Substring(indexTitleOpen, indexTitleClose - indexTitleOpen);

                    appName = XmlDecode(appName);

                    AppHttpUri = "http://redirect.zune.net/redirect?type=phoneapp&id=" +
                                 data.Guid.ToString();

                    AppUri = "zune://navigate/?phoneAppID=" + data.Guid.ToString();
                }
                else
                {
                    Error = "Unknown";
                }
            }
            else
            {
                Error = "Unknown";
            }

            Dispatcher.BeginInvoke(new Action(delegate()
            {
                if (Error == "")
                    AppName = appName;

                IsWorking = false;
                EnableLookupButton();
            }),
            null);

        }

        /// <summary>
        /// Quick utility functional replacement for HttpUtilities.HtmlEncode
        /// which doesn't seem to exist in the client profile.
        /// </summary>
        /// <param name="appName"></param>
        /// <returns></returns>
        private string XmlDecode(string appName)
        {
            return appName.Replace("&gt;", ">").Replace("&lt;", "<").Replace("&amp;", "&").Replace("&quot;", "\"");
        }

        private void EnableLookupButton()
        {
            btnLookup.IsEnabled = !IsWorking && !(string.IsNullOrWhiteSpace(txtGuid.Text));
        }

        /// <summary>
        /// clean the string (remove everything apart from the guid)
        /// app://B8DAB246-F043-E011-854C-00237DE2DB9E/_default => B8DAB246-F043-E011-854C-00237DE2DB9E
        /// 
        /// Note: sure, I could just use a regex to actually extract the guid + verify that it's actually a guid...
        /// </summary>
        /// <param name="guid"></param>
        /// <returns></returns>
        private static Guid CleanGuid(string guid)
        {
            int index;

            if (guid.StartsWith("app://"))
                guid = guid.Replace("app://", "");

            index = guid.IndexOf('/');

            if (index > -1)
                guid = guid.Substring(0, index);

            return new Guid(guid);
        }

        private void txtGuid_TextChanged(object sender, TextChangedEventArgs e)
        {
            ClearData();

            EnableLookupButton();
        }

        private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
        {
            try
            {
                Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
            }
            catch (Win32Exception)
            {
                try
                {
                    // failed to navigate via zune:// let's go via HTTP
                    Process.Start(new ProcessStartInfo(AppHttpUri));
                }
                catch (Win32Exception)
                {
                    MessageBox.Show("I tried showing you the app via Zune, but it looks like that isn't installed. I then tried to use your webbrowser, but again I was thwarted.\n\nYou're going to have to resort to searching on Bing...\n\nGood Luck!", "Failed to launch Zune and Browser", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                }
            }

            e.Handled = true;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        // NotifyPropertyChanged will raise the PropertyChanged event passing the
        // source property that is being updated.
        public void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}
